package com.redission.lock;

import java.util.concurrent.TimeUnit;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@SpringBootApplication
public class RedissionApplication {

	@Autowired
	private StringRedisTemplate redisTemplate;
	
	@Autowired
	private Redisson redisson;
	
	@Bean
	@ConditionalOnMissingBean(value = StringRedisTemplate.class)
	public StringRedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
	  StringRedisTemplate template = new StringRedisTemplate(connectionFactory);
	  template.setEnableTransactionSupport(true);
	  return template;
	}
	
	@Bean
	public Redisson setRedisson() {
		Config config = new Config();
		config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
		return (Redisson)Redisson.create(config);
	}
	
	
	public static void main(String[] args) {
		SpringApplication.run(RedissionApplication.class, args);
	}

	/**
	 * 方案1：直接执行扣库操作，有问题
	 * 由于JVM线程模型机制，出现各种数据重复执行和覆盖
	 */
//	@RequestMapping("/reduceProduct")
	public void plan1() {
		int num = Integer.parseInt(redisTemplate.opsForValue().get("num"));
		if(num>0){
			redisTemplate.opsForValue().set("num", (num-1) + "");
			System.out.println("扣减库存成功，库存剩余：" + (num-1));
		}else {
			System.out.println("扣减库存失败，库存不足");
		}
	}
	
	/**
	 * 方案2：加锁，数据能保持一致性，但是不完美
	 * 
	 * 思路：通过另外设置一个对象锁的方式，进行原子操作，然后竞争到的就能执行
	 * 
	 * 实现：redis的incr，是原子操作（对数字增加指定的数量），由于redis的线程模型是单线程，因此通过这种原子操作，所有任务都会进入队列中同步执行。
	 * 一般情况下，+1和-1是对等的，除非web应用挂掉了，导致没有-1操作，后续其他集群服务无法获取锁
	 * 
	 * 缺陷：
	 * 1）就算1000个人竞争10个资源，最后结果也很可能，有剩余，没有抢完，不合理
	 * 2）若是竞争到锁的线程，执行过程中抛出异常(异常可以通过finally保底)，或者web死掉，那么lock锁，一定是大于0的，因此后续线程，无法再获取执行权
	 * 
	 * 代码逻辑：
	 * 1）数值类型锁lock，初始值为0，每个线程都+1
	 * 2）如果lock=1时，证明没有人竞争到该锁，可以执行对应的逻辑
	 * 3）数值类型锁lock，每个线程都-1
	 *
	 */
//	@RequestMapping("/reduceProduct")
	public void plan2() {
		String lock = "lock";
		try {
			Long lockInt = redisTemplate.opsForValue().increment(lock, 1);
			if(lockInt==1) {
				int num = Integer.parseInt(redisTemplate.opsForValue().get("num"));
				if(num>0){
					redisTemplate.opsForValue().set("num", (num-1) + "");
					System.out.println("扣减库存成功，库存剩余：" + (num-1));
				}else {
					System.out.println("扣减库存失败，库存不足");
				}
			}
		}finally {
			redisTemplate.opsForValue().increment(lock, -1);
		}
	}
	
	/**
	 * 方案3：
	 * 在方案2的基础上，给锁增加一个超时时间，以防锁无法释放之类的情况
	 * 
	 * 缺陷：有可能执行逻辑的时间，比超时时间大，导致锁释放，所以需要自行给锁，续时（类似不够花费了，需要续费）
	 * 而且，成功率降低了，本来1000个人抢10，运行多次，大多数都是只有2个被抢成功
	 * 
	 */
//	@RequestMapping("/reduceProduct")
	public void plan3() {
		String lock = "lock";
		try {
			Long lockInt = redisTemplate.opsForValue().increment(lock, 1);
			redisTemplate.expire(lock, 10, TimeUnit.SECONDS);//设置超时时间
			if(lockInt==1) {
				int num = Integer.parseInt(redisTemplate.opsForValue().get("num"));
				if(num>0){
					redisTemplate.opsForValue().set("num", (num-1) + "");
					System.out.println("扣减库存成功，库存剩余：" + (num-1));
				}else {
					System.out.println("扣减库存失败，库存不足");
				}
			}
		}finally {
			redisTemplate.opsForValue().increment(lock, -1);
		}
	}
	
	/**
	 * 方案4：
	 * 使用redisson框架，基本完美实现功能
	 * 
	 * 原理：在方案3的基础上，多了一个监听锁，防止锁超时释放的问题
	 * 
	 * 缺陷：
	 * 1）自旋锁，判断是否已获取锁资源，而导致性能问题
	 * 2）redis主从架构，若是master的key还没复制到slave中，那么当master挂掉后，slave会变成master，即锁对象会丢失，锁被释放了。
	 * 
	 */
	@RequestMapping("/reduceProduct")
	public void plan4() {
		RLock lock = redisson.getLock("redissonLock");
		try {
			lock.lock();
			int num = Integer.parseInt(redisTemplate.opsForValue().get("num"));
			if(num>0){
				redisTemplate.opsForValue().set("num", (num-1) + "");
				System.out.println("扣减库存成功，库存剩余：" + (num-1));
			}else {
				System.out.println("扣减库存失败，库存不足");
			}
		}finally {
			lock.unlock();
		}
	}
	
}
